﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Microsoft.Extensions.DependencyInjection;
using InTheHand.Net;
using MyTRCP.Common.MyProtocol;

namespace MyTRCP.Common.Services
{
    public class MyProtocolBridgeSideService : BackgroundService
    {
        private readonly ILogger<MyProtocolBridgeSideService> _logger;
        private readonly ILogger<BridgeSide> _bsLogger;
        private int _port;
        private readonly AsymmetricCipherKeyPair _idKeyPair;
        private readonly List<ECPublicKeyParameters> _trustedClientPublicKeys;
        private readonly AutoResetEvent _openEvent;
        private readonly AutoResetEvent _closeEvent;
        private readonly DevCtrlLinkService _dcl;
        private readonly DevAudioLinkService _dal;
        private BridgeSide? _bs;
        private readonly BluetoothAddress _ba;
        private int _repeatTimes;
        public int RepeatTimes
        {
            get
            {
                if (_bs != null)
                {
                    return _bs.EachPayloadRepeatTimes;
                }
                else return _repeatTimes;
            }
            set
            {
                if (_bs != null)
                {
                    _bs.EachPayloadRepeatTimes = value;
                }
                _repeatTimes = value;
            }
        }
        public MyProtocolBridgeSideService(ILogger<MyProtocolBridgeSideService> logger, ILogger<BridgeSide> bsLogger, IConfiguration conf, DevAudioLinkService idal, DevCtrlLinkService idcl)
        {
            logger.LogDebug($"MyProtocolBridgeSideService creating...");
            _logger = logger;
            _bsLogger = bsLogger;
            _trustedClientPublicKeys = new List<ECPublicKeyParameters>();
            _openEvent = new AutoResetEvent(false);
            _closeEvent = new AutoResetEvent(false);
            _dcl = idcl;
            _dal = idal;
            IConfigurationSection pubKx = conf.GetRequiredSection("MyProtocol:BridgeSide:IdAuthParam:PublicKey:X");
            IConfigurationSection pubKy = conf.GetRequiredSection("MyProtocol:BridgeSide:IdAuthParam:PublicKey:Y");
            IConfigurationSection priK = conf.GetRequiredSection("MyProtocol:BridgeSide:IdAuthParam:PrivateKey");
            IConfigurationSection clientPubKs = conf.GetRequiredSection("MyProtocol:BridgeSide:TrustedClientPublicKeys");
            IConfigurationSection lp = conf.GetRequiredSection("MyProtocol:BridgeSide:ListenPort");
            IConfigurationSection dba = conf.GetRequiredSection("MyProtocol:BridgeSide:DeviceBluetoothAddress");
            IConfigurationSection rt = conf.GetRequiredSection("MyProtocol:RepeatTimes");
            if (rt.Value != null && int.TryParse(rt.Value, out int irt))
            {
                _repeatTimes = irt;
            }
            else
            {
                _repeatTimes = 3;
            }
            if (lp.Value != null && ushort.TryParse(lp.Value, out ushort ilp))
            {
                _port = ilp;
            }
            else
            {
                _port = 65233;
            }
            if (dba.Value != null && BluetoothAddress.TryParse(dba.Value, out BluetoothAddress bluetoothAddress))
            {
                _ba = bluetoothAddress;
            }
            else
            {
                _ba = new BluetoothAddress(0x38d20000aae6ul);
            }
            var list = clientPubKs.GetChildren();
            if (pubKx.Value != null && pubKy.Value != null && priK.Value != null)
            {
                var pubk2 = MyProtocolCryptoHelper.ECPublicKeyFromMyBytes(Convert.FromBase64String(pubKx.Value), Convert.FromBase64String(pubKy.Value));
                var prik2 = MyProtocolCryptoHelper.ECPrivateKeyFromMyBytes(Convert.FromBase64String(priK.Value));
                _idKeyPair = new AsymmetricCipherKeyPair(pubk2, prik2);
            }
            else
            {
                _idKeyPair = MyProtocolCryptoHelper.GenerateFixedECDHKeyPair(233);
                _trustedClientPublicKeys.Add((ECPublicKeyParameters)MyProtocolCryptoHelper.GenerateFixedECDHKeyPair(234).Public);
            }

            foreach (var k in list)
            {
                IConfigurationSection xs = k.GetRequiredSection("X");
                IConfigurationSection ys = k.GetRequiredSection("Y");
                if (xs.Value != null && ys.Value != null)
                {
                    _trustedClientPublicKeys.Add(MyProtocolCryptoHelper.ECPublicKeyFromMyBytes(Convert.FromBase64String(xs.Value), Convert.FromBase64String(ys.Value)));
                }
            }
        }

        private void _dal_Disconnected(object? sender, EventArgs e)
        {
            if (_bs != null)
            {
                _bs.SendDevConnectStatusChangedNotice(DevConnectStatusChangedNoticeTypeEnum.AudioLinkDisconnected);
            }
        }

        private void _dal_Connected(object? sender, EventArgs e)
        {
            if (_bs != null)
            {
                _bs.SendDevConnectStatusChangedNotice(DevConnectStatusChangedNoticeTypeEnum.AudioLinkConnected);
            }
        }

        private void _dcl_Disconnected(object? sender, EventArgs e)
        {
            if (_bs != null)
            {
                _bs.SendDevConnectStatusChangedNotice(DevConnectStatusChangedNoticeTypeEnum.CtrlLinkDisconnected);
            }
        }

        private void _dcl_Connected(object? sender, EventArgs e)
        {
            if (_bs != null)
            {
                _bs.SendDevConnectStatusChangedNotice(DevConnectStatusChangedNoticeTypeEnum.CtrlLinkConnected);
            }
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Run(() =>
            {
                try
                {
                    while (!stoppingToken.IsCancellationRequested)
                    {
                        try
                        {
                            _openEvent.WaitOne();
                            _bs = new BridgeSide(_bsLogger, _port, _idKeyPair, _trustedClientPublicKeys, _dcl.PayloadRecvQueue, _dal.PayloadRecvQueue, _dcl.PayloadSendQueue, _dal.PayloadSendQueue, RemoteDevLinkOp, GettingRemoteDevLinkStatus, _dcl.DiscoverMyDevicesAsync, _repeatTimes);
                            _bs.Start();
                            _closeEvent.WaitOne();
                            _bs.Stop();
                        }
                        catch (OperationCanceledException)
                        {
                            _logger.LogInformation("桥接端执行循环内收到操作取消信号，退出");
                            break;
                        }
                        catch (Exception e)
                        {
                            _logger.LogError(e, "桥接端执行循环内出现异常");
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "桥接端执行过程中出现异常");
                }
            }, stoppingToken);
        }

        private Task<RemoteDevLinkStatusArgs> GettingRemoteDevLinkStatus()
        {
            RemoteDevLinkStatusArgs args = new RemoteDevLinkStatusArgs
            {
                OpResult = 1,
                IsDevCtrlConneccted = _dcl.IsConnected,
                IsDevAudioConnected = _dal.IsConnected
            };
            return Task.FromResult(args);
        }

        private async Task<int> RemoteDevLinkOp(DevLinkOperationTypeEnum opType, BluetoothAddress? ba = null)
        {
            switch (opType)
            {
                case DevLinkOperationTypeEnum.ConnectControlLink:
                    if (_dcl.IsConnected)
                    {
                        return -1;
                    }
                    else
                    {
                        return await _dcl.OpenAsync(ba ?? _ba) ? 1 : -2;
                    }
                case DevLinkOperationTypeEnum.DisconnectControlLink:
                    if (_dcl.IsConnected)
                    {
                        return await _dcl.CloseAsync() ? 1 : -2;
                    }
                    else
                    {
                        return -1;
                    }
                case DevLinkOperationTypeEnum.ConnectAudioLink:
                    if (_dal.IsConnected)
                    {
                        return -1;
                    }
                    else
                    {
                        return await _dal.OpenAsync(ba ?? _ba) ? 1 : -2;
                    }
                case DevLinkOperationTypeEnum.DisconnectAudioLink:
                    if (_dal.IsConnected)
                    {
                        return await _dcl.CloseAsync() ? 1 : -2;
                    }
                    else
                    {
                        return -1;
                    }
            }
            return 0;
        }

        public void Open(int? port = null)
        {
            if (_bs == null || !_bs.RunningStatus)
            {
                if (port != null && port.HasValue)
                {
                    ArgumentOutOfRangeException.ThrowIfGreaterThan(port.Value, 65535, nameof(port));
                    _port = port.Value;
                }
                _openEvent.Set();
            }
        }
        public void Close()
        {
            if (_bs != null && _bs.RunningStatus)
            {
                _closeEvent.Set();
            }
        }
        public override async Task StartAsync(CancellationToken cancellationToken)
        {
            _dcl.Connected += _dcl_Connected;
            _dcl.Disconnected += _dcl_Disconnected;
            _dal.Connected += _dal_Connected;
            _dal.Disconnected += _dal_Disconnected;
            await _dal.StartAsync(cancellationToken);
            await _dcl.StartAsync(cancellationToken);
            Open();
            await base.StartAsync(cancellationToken);
        }
        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            _dcl.Connected -= _dcl_Connected;
            _dcl.Disconnected -= _dcl_Disconnected;
            _dal.Connected -= _dal_Connected;
            _dal.Disconnected -= _dal_Disconnected;
            await _dal.StopAsync(cancellationToken);
            await _dcl.StopAsync(cancellationToken);
            Close();
            await base.StopAsync(cancellationToken);
        }
    }
}
